package ph.com.gs3.formalistics.service.services;

import ph.com.gs3.formalistics.global.interfaces.CallbackCommand;
import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.global.utilities.services.ServiceEventNotifierUtility;
import ph.com.gs3.formalistics.model.communicators.HttpCommunicatorModule.ResultCode;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.presenter.MainActivity;
import ph.com.gs3.formalistics.service.managers.SessionManager;
import ph.com.gs3.formalistics.service.managers.SessionManager.CheckConnectionResult;
import ph.com.gs3.formalistics.service.managers.SessionManager.LoginResult;
import ph.com.gs3.formalistics.service.managers.SessionManager.Mode;
import ph.com.gs3.formalistics.service.managers.SessionManager.SessionManagerEventListener;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

/**
 * Front end service. Manages other services running in the background. To start this
 * service, invoke the startFullUpdate method, this method will then check for current
 * user's session or wait till it's available. If the session is available, then it will
 * proceed to starting up the FormsUpdateService up to the CommentsUpdateService, if not,
 * it will first start AuthenticationService to do an automatic login then start up the
 * other services.
 * 
 * @author Ervinne Sodusta
 */
public class FullDataUpdateService extends Service {

	public static final String TAG = FullDataUpdateService.class.getSimpleName();

	public static final String EXTRA_CURRENT_USER = "current_user";

	// private static final boolean isCurrentlyRunning = false;

	private SessionManager sessionMan;

	// Binder given to clients
	private final IBinder binder = new FullDataUpdateServiceBinder();
	private FullDataUpdateListener listener;

	// Data Fields
	private User currentUser;

	@Override
	public void onCreate() {
		super.onCreate();
	}

	@Override
	public int onStartCommand(final Intent intent, int flags, int startId) {

		sessionMan = SessionManager.getApplicationInstance();
		if (!sessionMan.isCurrentlyCheckingServerConnection()
		        && sessionMan.getMode() == Mode.OFFLINE) {
			sessionMan
			        .checkConnectionToServer(new CallbackCommand<SessionManager.CheckConnectionResult>() {

				        @Override
				        public void execute(CheckConnectionResult result) {
					        initializeDataFromIntent(intent);
					        // startFullUpdate();
				        }
			        });

			// ServiceEventNotifierUtility.getDefaultInstance().displayBasicNotification(this,
			// "Checking connection to server.");

		} else {
			initializeDataFromIntent(intent);
			// startFullUpdate();
		}

		return START_REDELIVER_INTENT;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		FLLogger.d(TAG, "Service destroyed");

		// dismiss notification
		ServiceEventNotifierUtility.getDefaultInstance().dismissPrimaryNotification(this);

		unregisterBroadcastReceivers();

	}

	// ========================================================================
	// {{ Initialization & Functional Methods

	private void initializeDataFromIntent(Intent intent) {

		Bundle extras = intent.getExtras();

		currentUser = (User) extras.getSerializable(EXTRA_CURRENT_USER);
		// server = extras.getString(EXTRA_SERVER);
		// companyId = extras.getString(EXTRA_ACTIVE_USER_COMPANY_ID);
		// currentUserEmail = extras.getString(EXTRA_ACTIVE_USER_EMAIL);
		// currentUserPassword = extras.getString(EXTRA_ACTIVE_USER_PASSWORD);

	}

	public void startFullUpdate(FullDataUpdateListener listener) {

		this.listener = listener;

		registerBroadcastReceivers();

		// get the connection & session status from the session manager
		if (sessionMan.isCurrentlyCheckingServerConnection()) {
			FLLogger.d(TAG,
			        "SessionManager is still currently checking connection, service will wait until it's done.");

			SessionManagerEventListener eventListener = new SessionManagerEventListener() {

				@Override
				public void onSessionLost() {
					FLLogger.d(TAG, "Session lost");
					// NotificationManager notificationManager = (NotificationManager)
					// getSystemService(NOTIFICATION_SERVICE);
					// notificationManager.cancel(13);
					// FLLogger.d(TAG, "Cancel called");

					ServiceEventNotifierUtility.getDefaultInstance().dismissPrimaryNotification(
					        FullDataUpdateService.this);

					notifyLostSession();
					finishFullUpdate();
				}

				@Override
				public void onModeChanged(Mode mode) {

					ServiceEventNotifierUtility.getDefaultInstance().dismissPrimaryNotification(
					        FullDataUpdateService.this);

					decideWhichServiceToStart();
					sessionMan.removeListener(this);
				}
			};
			sessionMan.addListener(eventListener);

			// notify
			Bundle notificationExtras = new Bundle();
			notificationExtras.putSerializable(MainActivity.EXTRA_LOGGED_IN_USER, currentUser);

			ServiceEventNotifierUtility.getDefaultInstance().displayBasicNotification(this,
			        ServiceEventNotifierUtility.NOTIFICATION_ID_PRIMARY,
			        "Checking connection to server.", notificationExtras);
		} else {
			decideWhichServiceToStart();
		}

	}

	private void finishFullUpdate() {

		if (listener != null) {
			listener.onFullUpdateDone();
		}

		// true = remove notification
		// stopForeground(true);
		stopSelf();

	}

	private void notifyLostSession() {
		listener.onUserNeedsToReauthenticate();
	}

	private void decideWhichServiceToStart() {

		if (sessionMan.getMode() == Mode.OFFLINE) {
			// the application is currently unable to connect to server, nothing to do
			// here
			FLLogger.d(TAG,
			        "FullUpdateService stopped, the application is not connected to the server - "
			                + currentUser.getAccountServer());
			finishFullUpdate();
		} else if (sessionMan.getMode() == Mode.ONLINE && sessionMan.isAuthenticated()) {
			FLLogger.d(TAG,
			        "Application is already authenticated, starting outgoing documents update service");
			// startFormsUpdateService();
			startOutgoingDocumentsSubmitService();
		} else if (sessionMan.getMode() == Mode.ONLINE && !sessionMan.isAuthenticated()) {
			FLLogger.d(TAG, "Application does not have session yet, starting AuthenticationService");
			startAuthenticationService();
		}
	}

	private void registerBroadcastReceivers() {

		// register authentication broadcast receiver

		// @formatter:off
		IntentFilter authenticationBroadcastReceiverFilter = new IntentFilter();		
		authenticationBroadcastReceiverFilter.addAction(AuthenticationService.ACTION_ON_LOGIN_SERVICE_DONE);
		authenticationBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(authenticationResultBroadcastListener, authenticationBroadcastReceiverFilter);
		// @formatter:on

		// register forms update broadcast receiver

		IntentFilter formsUpdateBroadcastReceiverFilter = new IntentFilter();
		formsUpdateBroadcastReceiverFilter.addAction(FormsUpdateService.ACTION_ON_UPDATE_DONE);
		formsUpdateBroadcastReceiverFilter.addAction(FormsUpdateService.ACTION_ON_UPDATE_ERROR);
		formsUpdateBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(formsUpdateBroadcastReceiver, formsUpdateBroadcastReceiverFilter);

		// register documents update broadcast receiver

		IntentFilter documentsUpdateBroadcastReceiverFilter = new IntentFilter();
		// @formatter:off
		// Note, we don't need to listen to service start broadcasts
		documentsUpdateBroadcastReceiverFilter.addAction(DocumentsUpdateService.ACTION_ON_UPDATE_DONE);
		documentsUpdateBroadcastReceiverFilter.addAction(DocumentsUpdateService.ACTION_ON_DOCUMENT_UPDATED);
		documentsUpdateBroadcastReceiverFilter.addAction(DocumentsUpdateService.ACTION_ON_DOCUMENT_UPDATE_ERROR);
		documentsUpdateBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(documentsUpdateBroadcastReceiver, documentsUpdateBroadcastReceiverFilter);
		// @formatter:on

		// register outgoing document action submit broadcast receiver

		IntentFilter outgoingDocumentActionSubmitBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		outgoingDocumentActionSubmitBroadcastReceiverFilter.addAction(OutgoingDocumentActionSubmitService.ACTION_ON_EXISTING_DOCUMENT_ACTION_SUBMITTED);
		outgoingDocumentActionSubmitBroadcastReceiverFilter.addAction(OutgoingDocumentActionSubmitService.ACTION_ON_NEW_DOCUMENT_SUBMITTED);
		outgoingDocumentActionSubmitBroadcastReceiverFilter.addAction(OutgoingDocumentActionSubmitService.ACTION_ON_SERVICE_DONE);
		outgoingDocumentActionSubmitBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(outgoingDocumentActionSubmitBroadcastReceiver, outgoingDocumentActionSubmitBroadcastReceiverFilter);
		// @formatter:on

		IntentFilter outgoingStarMarksSubmitBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		outgoingStarMarksSubmitBroadcastReceiverFilter.addAction(OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_DONE);
		outgoingStarMarksSubmitBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(outgoingStarMarksSubmitBroadcastReceiver, outgoingStarMarksSubmitBroadcastReceiverFilter);
		// @formatter:on

		// register outgoing comments submit broadcast receiver

		IntentFilter outgoingCommentsSubmitBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		outgoingCommentsSubmitBroadcastReceiverFilter.addAction(OutgoingCommentsSubmitService.ACTION_ON_SERVICE_STARTED);
		outgoingCommentsSubmitBroadcastReceiverFilter.addAction(OutgoingCommentsSubmitService.ACTION_ON_SERVICE_DONE);
		outgoingCommentsSubmitBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(outgoingCommentsSubmitBroadcastReceiver, outgoingCommentsSubmitBroadcastReceiverFilter);
		// @formatter:on

		// register delete comments broadcast receiver

		IntentFilter deleteCommentBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		deleteCommentBroadcastReceiverFilter.addAction(DeleteCommentsMarkedForDeletionService.ACTION_ON_SERVICE_STARTED);
		deleteCommentBroadcastReceiverFilter.addAction(DeleteCommentsMarkedForDeletionService.ACTION_ON_SERVICE_DONE);
		deleteCommentBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		
		registerReceiver(deleteCommentBroadcastReceiver, deleteCommentBroadcastReceiverFilter);
		// @formatter:on

		// register comments update broadcast receiver

		IntentFilter commentsUpdateBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		commentsUpdateBroadcastReceiverFilter.addAction(CommentsUpdateService.ACTION_ON_UPDATE_STARTED);
		commentsUpdateBroadcastReceiverFilter.addAction(CommentsUpdateService.ACTION_ON_UPDATE_DONE);		
		commentsUpdateBroadcastReceiverFilter.addAction(CommentsUpdateService.ACTION_ON_DOCUMENT_COMMENTS_UPDATED);
		commentsUpdateBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		
		registerReceiver(commentsUpdateBroadcastReceiver, commentsUpdateBroadcastReceiverFilter);
		// @formatter:on

	}

	private void unregisterBroadcastReceivers() {
		unregisterReceiver(authenticationResultBroadcastListener);

		unregisterReceiver(outgoingDocumentActionSubmitBroadcastReceiver);
		unregisterReceiver(outgoingStarMarksSubmitBroadcastReceiver);
		unregisterReceiver(outgoingCommentsSubmitBroadcastReceiver);

		unregisterReceiver(formsUpdateBroadcastReceiver);
		unregisterReceiver(documentsUpdateBroadcastReceiver);
		unregisterReceiver(deleteCommentBroadcastReceiver);
		unregisterReceiver(commentsUpdateBroadcastReceiver);
	}

	// }}

	// ========================================================================
	// {{ Service Starters

	private void startAuthenticationService() {
		Intent authenticationServiceIntent = new Intent(FullDataUpdateService.this,
		        AuthenticationService.class);

		String server = currentUser.getAccountServer();
		String email = currentUser.getEmail();
		String password = currentUser.getPassword();

		authenticationServiceIntent.putExtra(AuthenticationService.EXTRA_SERVER, server);
		authenticationServiceIntent.putExtra(AuthenticationService.EXTRA_EMAIL, email);
		authenticationServiceIntent.putExtra(AuthenticationService.EXTRA_PASSWORD, password);

		startService(authenticationServiceIntent);
	}

	private void startFormsUpdateService() {
		Intent formsUpdateServiceIntent = new Intent(FullDataUpdateService.this,
		        FormsUpdateService.class);

		formsUpdateServiceIntent.putExtra(FormsUpdateService.EXTRA_CURRENT_USER, currentUser);

		startService(formsUpdateServiceIntent);

	}

	private void startDocumentsUpdateService() {

		Intent documentsServiceIntent = new Intent(FullDataUpdateService.this,
		        DocumentsUpdateService.class);

		documentsServiceIntent.putExtra(DocumentsUpdateService.EXTRA_CURRENT_USER, currentUser);

		startService(documentsServiceIntent);

	}

	private void startDeleteCommentsService() {

		// get the active user from the manager
		User currentlyActiveUser = sessionMan.getLastActiveUserFromDatabase();

		Intent deleteCommentsService = new Intent(FullDataUpdateService.this,
		        DeleteCommentsMarkedForDeletionService.class);
		deleteCommentsService.putExtra(OutgoingCommentsSubmitService.EXTRA_CURRENT_USER,
		        currentlyActiveUser);
		startService(deleteCommentsService);

	}

	private void startCommentsUpdateService() {

		// get the active user from the manager
		User currentlyActiveUser = sessionMan.getLastActiveUserFromDatabase();

		Intent commentsUpdateServiceIntent = new Intent(FullDataUpdateService.this,
		        CommentsUpdateService.class);

		commentsUpdateServiceIntent.putExtra(CommentsUpdateService.EXTRA_CURRENT_USER,
		        currentlyActiveUser);

		startService(commentsUpdateServiceIntent);

	}

	// }}

	// ========================================================================
	// {{ Outgoing Service Starters

	private void startOutgoingDocumentsSubmitService() {

		// get the active user from the manager
		User currentlyActiveUser = sessionMan.getLastActiveUserFromDatabase();

		Intent intent = new Intent(FullDataUpdateService.this,
		        OutgoingDocumentActionSubmitService.class);
		intent.putExtra(OutgoingDocumentActionSubmitService.EXTRA_CURRENT_USER, currentlyActiveUser);

		startService(intent);

	}

	private void startOutgoingStarMarksSubmitService() {

		User currentlyActiveUser = sessionMan.getLastActiveUserFromDatabase();

		Intent intent = new Intent(FullDataUpdateService.this, OutgoingStarMarksSubmitService.class);
		intent.putExtra(OutgoingStarMarksSubmitService.EXTRA_CURRENT_USER, currentlyActiveUser);
		startService(intent);

	}

	private void startOutgoingCommentsSubmitService() {

		User currentlyActiveUser = sessionMan.getLastActiveUserFromDatabase();

		Intent intent = new Intent(FullDataUpdateService.this, OutgoingCommentsSubmitService.class);
		intent.putExtra(OutgoingCommentsSubmitService.EXTRA_CURRENT_USER, currentlyActiveUser);
		startService(intent);

	}

	// }}

	// ========================================================================
	// {{ Broadcast Receivers

	private BroadcastReceiver authenticationResultBroadcastListener = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			LoginResult results = (LoginResult) intent.getExtras().getSerializable(
			        AuthenticationService.EXTRA_LOGIN_RESULTS);

			if (ResultCode.SUCCESS.equals(results.getLoginResultCode())) {
				startFormsUpdateService();
			} else {
				FLLogger.d(TAG, "Login failed: " + results.getMessage());
				sessionMan.logout();
				notifyLostSession();
				finishFullUpdate();
			}
		}

	};

	private BroadcastReceiver formsUpdateBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {

			String action = intent.getAction();

			if (FormsUpdateService.ACTION_ON_UPDATE_DONE.equals(action)) {
				// TODO: call documents update service here
				FLLogger.d(TAG, "forms updating done");
			} else if (FormsUpdateService.ACTION_ON_UPDATE_ERROR.equals(action)) {
				// TODO: report error here, then update documents
				FLLogger.d(TAG, "forms updating encountered an error");
			} else {
				// Unknown action
				Log.w(TAG, "Anknown action - " + action
				        + "  was received by formsUpdateBroadcastReceiver.");
			}

			startDocumentsUpdateService();

		}

	};

	private BroadcastReceiver outgoingDocumentActionSubmitBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();

			if (OutgoingDocumentActionSubmitService.ACTION_ON_SERVICE_DONE.equals(action)) {
				FLLogger.d(TAG, "OutgoingDocumentActionSubmitService done executing");
				startOutgoingStarMarksSubmitService();
			}

		}

	};

	private BroadcastReceiver outgoingStarMarksSubmitBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();

			if (OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_DONE.equals(action)) {
				FLLogger.d(TAG, "OutgoingStarMarksSubmitService done executing");
				startOutgoingCommentsSubmitService();
			}
		}

	};

	private BroadcastReceiver outgoingCommentsSubmitBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();

			if (OutgoingCommentsSubmitService.ACTION_ON_SERVICE_DONE.equals(action)) {

				FLLogger.d(TAG, "OutgoingCommentsSubmitService done executing");
				startFormsUpdateService();

			}

		}

	};

	private BroadcastReceiver documentsUpdateBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {

			String action = intent.getAction();

			if (DocumentsUpdateService.ACTION_ON_UPDATE_DONE.equals(action)) {
				FLLogger.d(TAG, "DocumentsUpdateService done executing");

				startDeleteCommentsService();
			}

		}

	};

	private BroadcastReceiver deleteCommentBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();

			if (DeleteCommentsMarkedForDeletionService.ACTION_ON_SERVICE_DONE.equals(action)) {
				FLLogger.d(TAG, "DeleteCommentsMarkedForDeletionService done executing");
				startCommentsUpdateService();
			}

		}

	};

	private BroadcastReceiver commentsUpdateBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {

			String action = intent.getAction();

			if (CommentsUpdateService.ACTION_ON_UPDATE_DONE.equals(action)) {
				FLLogger.d(TAG, "CommentsUpdateService done executing");
				finishFullUpdate();
			}

		}

	};

	// }}

	// ========================================================================
	// {{ Sub-classes & Interfaces

	public class FullDataUpdateServiceBinder extends Binder {
		public FullDataUpdateService getService() {
			// Return this instance of LocalService so clients can call public methods
			return FullDataUpdateService.this;
		}
	}

	public static interface FullDataUpdateListener {

		public void onUserNeedsToReauthenticate();

		public void onFullUpdateDone();

	}

	// }}

}
